package com.XZDP.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.XZDP.utils.Constant.RedisConstant;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.XZDP.BloomFilter.BloomFilterService;
import com.XZDP.dto.Result;
import com.XZDP.entity.RedisData;
import com.XZDP.entity.Shop;
import com.XZDP.mapper.ShopMapper;
import com.XZDP.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.XZDP.utils.CacheUtils;
import com.XZDP.utils.Constant.SystemConstants;
import com.XZDP.utils.MyUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private MyUtils myUtils;

    @Autowired
    private BloomFilterService bloomFilterService;

    @Autowired
    private CacheUtils cacheUtils;

    //建立线程池,设置十个线程数
    private static final ExecutorService Thread_pools = Executors.newFixedThreadPool(10);
    private RBloomFilter<String> ShopbloomFilter = null;

    @PostConstruct
    public void initBloomFilter() {
        ShopbloomFilter = bloomFilterService.createBloomFilter("shop", 10000, 0.03);
        // 预热布隆过滤器，将所有商铺ID存入
        List<Shop> allShops = list();
        for (Shop shop : allShops) {
            ShopbloomFilter.add(shop.getId().toString());
        }
        log.info("布隆过滤器预热成功");
    }


    /**
     * 商铺查询
     *
     * @param id
     * @return
     */
    public Shop GetById(Long id) throws JsonProcessingException {
        //查询前添加先通过布隆过滤器
        //构造布隆过滤器
        RBloomFilter<String> bloomFilter = bloomFilterService.createBloomFilter("shop", 10000, 0.03);
        // 校验布隆过滤器中是否存在该商铺ID
        if (!bloomFilter.contains(id.toString())) {
            log.info("布隆过滤器未查询到此参数,无效id:{}", id);
            // 如果不存在，直接返回null
            return null;
        }
        //利用互斥锁解决缓存击穿的实现方案
        Shop shop = Mutex(id);
        //利用互斥锁+逻辑过期解决缓存击穿的方案
        //Shop shop = LogicalExpiration(id);
        //通用函数调用，不解决缓存击穿
        //Shop shop = cacheUtils.GetRedisByKey("shop:", Shop.class, id, this::getById);
        //Shop shop = cacheUtils.GetRedisWithLogicalExpiration("shop:", Shop.class, id, this::getById, (long) (30 * 60 * 1000), "Lock:shop");
        return shop;
    }


    /**
     * 互斥锁+逻辑过期解决缓存击穿的方案
     */
    public Shop LogicalExpiration(Long id) {
        //查询缓存中是否有数据
        String s = redisTemplate.opsForValue().get("shop:" + id);
        //没有查询到直接返回Null
        if (s == null) {
            return null;
        }
        //将其转换成RedisData
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject jsonObject = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(jsonObject, Shop.class);
        //查询到数据,判断是否逻辑过期
        //获取当前的时间戳
        Long time = System.currentTimeMillis();
        if (time > redisData.getExpired()) {
            //逻辑过期,需要先尝试获取互斥锁
            Boolean b = myUtils.tryGetLock("Lock:shop");
            if (b) {
                log.info("获取互斥锁成功");
                //双重检查缓存是否过期
                Boolean b1 = checkTime(id);
                log.info(String.valueOf(b1));
                if (b1) {
                    //获取锁成功,建立新的线程进行缓存重建,返回旧数据
                    //ShopMegThread shopMegThread = new ShopMegThread(redisTemplate, getById(id), myUtils, id);
                    //启动线程
                    //shopMegThread.start();
                    //用线程池代替自建线程,提高性能
                    Thread_pools.submit(() -> {
                        try {
                            Shop newshop = getById(id);
                            Thread.sleep(300);
                            RedisData NewRedisdata = new RedisData();
                            //进行缓存重建
                            NewRedisdata.setData(newshop);
                            NewRedisdata.setExpired(System.currentTimeMillis() + 30 * 60 * 1000);
                            // 创建ObjectMapper对象
                            ObjectMapper objectMapper = new ObjectMapper();
                            // 将对象转换为JSON字符串
                            String json = null;
                            json = objectMapper.writeValueAsString(NewRedisdata);
                            redisTemplate.opsForValue().set("shop:" + id, json);
                            log.info("缓存重建成功");
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        } finally {
                            myUtils.CloseLock("Lock:shop");
                            log.info("释放锁成功");
                        }
                    });
                    return shop;
                }
                myUtils.CloseLock("Lock:shop");
                log.info("释放锁成功");
            } else {
                //获取锁失败,直接返回旧数据
                return shop;
            }
        } else {
            //没有过期,直接返回数据
            return shop;
        }
        return shop;
    }

    /**
     * 封装是否过期逻辑
     */
    public boolean checkTime(Long id) {
        //查询缓存中是否有数据
        String s = redisTemplate.opsForValue().get("shop:" + id);
        //将其转换成RedisData
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        JSONObject jsonObject = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(jsonObject, Shop.class);
        //查询到数据,判断是否逻辑过期
        //获取当前的时间戳
        Long time = System.currentTimeMillis();
        if (time > redisData.getExpired()) {
            return true;
        }
        return false;
    }


    /**
     * 互斥锁实现
     */
    public Shop Mutex(Long id) throws JsonProcessingException {
        //查询缓存中是否有数据
        Map<Object, Object> ShopMap = redisTemplate.opsForHash().entries("shop:" + id);
        //没有缓存先尝试获取互斥锁,避免出现缓存击穿问题(热点key问题)
        if (ShopMap.isEmpty()) {
            try {
                //尝试获取互斥锁
                Boolean HasKey = myUtils.tryGetLock("Lock:shop" + id);
                if (!HasKey) {
                    //获取互斥锁失败,等待一会重试
                    log.info("获取锁失败");
                    Thread.sleep(50);
                    GetById(id);
                }
                log.info("获取锁成功");
                //获取成功,再次检查缓存,如果有数据,退出当前if ，执行存在缓存逻辑，锁也要释放
                ShopMap = redisTemplate.opsForHash().entries("shop:" + id);
                if (!ShopMap.isEmpty()) {
                    log.info("缓存中有数据");
                    // 释放互斥锁
                    myUtils.CloseLock("Lock:shop" + id);
                    // 从缓存中读取
                    Shop shop = BeanUtil.fillBeanWithMap(ShopMap, new Shop(), false);
                    return shop;
                }
                log.info("缓存中无数据");
                //如果获取成功,尝试查询数据库重建缓存
                Shop ShopMesg = getById(id);
                Thread.sleep(200);
                //店铺信息数据库中不存在
                if (ShopMesg == null) {
                    return null;
                }
                //数据库中查询成功
                //将Shop转成HashMap
                Map<String, Object> map = BeanUtil.beanToMap(ShopMesg);
                Map<String, String> StringMap = new HashMap<>();
                //处理String序列化问题
                StringMap = myUtils.ReturnStringMap(map);
                //将数据写入缓存并设置过期时间
                redisTemplate.opsForHash().putAll("shop:" + id, StringMap);
                Boolean expire = redisTemplate.expire("shop:" + id, Duration.ofMinutes(30));
                log.info("缓存重建成功");
                return ShopMesg;
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                //释放互斥锁
                myUtils.CloseLock("Lock:shop" + id);
                log.info("互斥锁释放成功");
            }
        }
        //查询到有数据
        //从缓存中读取
        //将ShopMap转为shop
        Shop shop = BeanUtil.fillBeanWithMap(ShopMap, new Shop(), false);
        return shop;
    }


    /**
     * 更新商铺信息,先更新数据库,在删除redis,尽可能减少线程安全问题的发生
     *
     * @param shop
     */
    @Override
    @Transactional
    //通过事务控制，保证一致性
    public Result updateByIdAndRedis(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("id不存在");
        }
        //更新数据库
        updateById(shop);
        //删除redis
        System.out.println(id);
        redisTemplate.delete("shop:" + id);
        return Result.ok();
    }

    @Override
    public void saveT(Shop shop) {
        save(shop);
        bloomFilterService.addElement(ShopbloomFilter, shop.getId().toString());
        log.info("布隆过滤器已更新");
    }


    /**
     * @param typeId  店铺类型
     * @param current 分页页码
     * @param x       用户坐标
     * @param y       用户坐标
     * @return 返回符合条件分页后的商户信息列表，里面包含距离信息
     */
    @Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        //TODO 使用GEO查询距离
        Page<Shop> page = query()
                .eq("type_id", typeId)
                .page(new Page<>(current, SystemConstants.DEFAULT_PAGE_SIZE));
        // 返回数据
        return Result.ok(page.getRecords());
    }


    @Override
    public boolean save(Shop entity) {
        return super.save(entity);
    }
}
